package pkg

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"mini-app/config"
	"mini-app/config/redis"
	"mini-app/internal/define"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/helper"
	"192.168.1.75/go-pkg/logx"
	wechat "github.com/silenceper/wechat/v2"
	"github.com/silenceper/wechat/v2/cache"
	"github.com/silenceper/wechat/v2/credential"
	"github.com/silenceper/wechat/v2/miniprogram"
	miniConfig "github.com/silenceper/wechat/v2/miniprogram/config"
	"github.com/silenceper/wechat/v2/miniprogram/qrcode"
)

type MiniProgram struct{}

func (MiniProgram) New() (*miniprogram.MiniProgram, error) {
	wc := wechat.NewWechat()
	redisOpts := &cache.RedisOpts{
		Host:     config.Config.Redis.Host + ":" + config.Config.Redis.Port,
		Password: config.Config.Redis.Password,
		Database: config.Config.Redis.Database,
	}
	redisCache := cache.NewRedis(context.Background(), redisOpts)
	wc.SetCache(redisCache)
	cfg := &miniConfig.Config{
		AppID:     config.Config.WechatMini.AppID,
		AppSecret: config.Config.WechatMini.Secret,
		Token:     "",
	}
	mp := wc.GetMiniProgram(cfg)

	mp.SetAccessTokenHandle(
		credential.NewStableAccessToken(
			config.Config.WechatMini.AppID,
			config.Config.WechatMini.Secret,
			"gowechat_officialaccount__access_token_"+config.Config.WechatMini.AppID,
			redisCache,
		),
	)
	return mp, nil
}

func (MiniProgram) NewAdvertisement() (*miniprogram.MiniProgram, error) {
	wc := wechat.NewWechat()
	redisOpts := &cache.RedisOpts{
		Host:     config.Config.Redis.Host + ":" + config.Config.Redis.Port,
		Password: config.Config.Redis.Password,
		Database: config.Config.Redis.Database,
	}
	redisCache := cache.NewRedis(context.Background(), redisOpts)
	wc.SetCache(redisCache)
	cfg := &miniConfig.Config{
		AppID:     config.Config.WechatMiniAdvertisement.AppID,
		AppSecret: config.Config.WechatMiniAdvertisement.Secret,
		Token:     "",
	}
	mp := wc.GetMiniProgram(cfg)

	mp.SetAccessTokenHandle(
		credential.NewStableAccessToken(
			config.Config.WechatMiniAdvertisement.AppID,
			config.Config.WechatMiniAdvertisement.Secret,
			"gowechat_officialaccount__access_token_"+config.Config.WechatMiniAdvertisement.AppID,
			redisCache,
		),
	)
	return mp, nil
}

type CheckSessionRsp struct {
	Errcode int    `json:"errcode"`
	Errmsg  string `json:"errmsg"`
}

func HmacSha256(message string, secret string) string {
	h := hmac.New(sha256.New, []byte(secret))
	// Write the message to the HMAC instance.
	_, err := h.Write([]byte(message))
	if err != nil {
		panic(err)
	}

	// Compute the HMAC-SHA256 digest.
	digest := h.Sum(nil)
	return hex.EncodeToString(digest)
}

func (MiniProgram) MiniCheckSession(ctx context.Context, token string) (err error) {
	mp, err := MiniProgram{}.New()
	if err != nil {
		return
	}
	rdb := redis.NewDB()
	openid, _ := rdb.Get(ctx, define.MiniAppSessionOpenid+token).Result()
	sessionKey, _ := rdb.Get(ctx, define.MiniAppSessionKey+token).Result()
	accessToken, err := mp.GetAuth().GetAccessToken()
	if err != nil {
		logx.Error(ctx, "mini check session err", logx.Any("accessToken", err.Error()))
		err = errorx.New("小程序请求失败", -1)
		return
	}
	signature := HmacSha256("", sessionKey)
	uri := fmt.Sprintf("https://api.weixin.qq.com/wxa/checksession?access_token=%s&signature=%s&openid=%s&sig_method=%s",
		accessToken, signature, openid, "hmac_sha256")
	statusCode, bodyBytes, err := helper.Net{}.Request(ctx, "GET", uri, nil, nil)
	if statusCode != 200 || err != nil {
		logx.Error(ctx, "mini check session err", logx.Any("err", err.Error()))
		err = errorx.New("小程序请求失败", -1)
		return
	}
	var CheckSessionRsp CheckSessionRsp
	json.Unmarshal(bodyBytes, &CheckSessionRsp)

	if CheckSessionRsp.Errcode == 0 { // 未失效
		return
	}
	logx.Error(ctx, "mini check session err",
		logx.Any("token", token),
		logx.Any("accessToken", accessToken),
		logx.Any("err code", CheckSessionRsp.Errcode),
		logx.Any("err msg", CheckSessionRsp.Errmsg))
	// 失效后删除缓存
	rdb.Del(ctx, define.MiniAppSessionOpenid+token)
	rdb.Del(ctx, define.MiniAppSessionKey+token)
	err = errorx.New("请重新登录", 100099)
	return
}

func (MiniProgram) MiniCheckSessionAdvertisement(ctx context.Context, token string) (err error) {
	mp, err := MiniProgram{}.NewAdvertisement()
	if err != nil {
		return
	}
	rdb := redis.NewDB()
	openid, _ := rdb.Get(ctx, define.MiniAppSessionOpenid+token).Result()
	sessionKey, _ := rdb.Get(ctx, define.MiniAppSessionKey+token).Result()
	accessToken, err := mp.GetAuth().GetAccessToken()
	if err != nil {
		logx.Error(ctx, "mini check session err", logx.Any("accessToken", err.Error()))
		err = errorx.New("小程序请求失败", -1)
		return
	}
	signature := HmacSha256("", sessionKey)
	uri := fmt.Sprintf("https://api.weixin.qq.com/wxa/checksession?access_token=%s&signature=%s&openid=%s&sig_method=%s",
		accessToken, signature, openid, "hmac_sha256")
	statusCode, bodyBytes, err := helper.Net{}.Request(ctx, "GET", uri, nil, nil)
	if statusCode != 200 || err != nil {
		logx.Error(ctx, "mini check session err", logx.Any("err", err.Error()))
		err = errorx.New("小程序请求失败", -1)
		return
	}
	var CheckSessionRsp CheckSessionRsp
	json.Unmarshal(bodyBytes, &CheckSessionRsp)

	if CheckSessionRsp.Errcode == 0 { // 未失效
		return
	}
	logx.Error(ctx, "mini check session err",
		logx.Any("token", token),
		logx.Any("accessToken", accessToken),
		logx.Any("err code", CheckSessionRsp.Errcode),
		logx.Any("err msg", CheckSessionRsp.Errmsg))
	// 失效后删除缓存
	rdb.Del(ctx, define.MiniAppSessionOpenid+token)
	rdb.Del(ctx, define.MiniAppSessionKey+token)
	err = errorx.New("请重新登录", 100099)
	return
}

type GetGetUnlimitedQRCodeRsp struct {
	Buffer  string `json:"buffer"`
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
}

func (MiniProgram) GetUnlimitedQRCode(ctx context.Context, page, scene, env string, isHyaline bool) (
	data string, err error,
) {
	mp, err := MiniProgram{}.New()
	if err != nil {
		return
	}
	checkPath := false // 暂时不验证页面
	rspBytes, err := mp.GetQRCode().GetWXACodeUnlimit(qrcode.QRCoder{
		Page:       page,
		Scene:      scene,
		EnvVersion: env,
		IsHyaline:  isHyaline,
		CheckPath:  &checkPath,
	})
	if err != nil {
		logx.Error(ctx, "mini check session err", logx.Any("err", err.Error()))
		err = errorx.New("小程序请求失败", -1)
	}
	var Rsp GetGetUnlimitedQRCodeRsp
	decodeErr := json.Unmarshal(rspBytes, &Rsp)
	if decodeErr != nil { // 成功返回图片buff,失败返回json
		// bbb := bytes.NewBuffer(bodyBytes) // buffer
		// m, _, _ := image.Decode(bbb)      // 图片文件解码
		// f, _ := os.Create("test.jpg")     // 创建文件
		// defer f.Close()                   // 关闭文件
		// jpeg.Encode(f, m, nil)            // 写入文件
		return base64.StdEncoding.EncodeToString(rspBytes), nil
	}

	if Rsp.ErrCode == 0 { // 成功
		return Rsp.Buffer, nil
	}
	return
}

func (MiniProgram) GetUnlimitedQRCodeAdvertisement(ctx context.Context, page, scene, env string, isHyaline bool) (
	data string, err error,
) {
	mp, err := MiniProgram{}.NewAdvertisement()
	if err != nil {
		return
	}
	checkPath := false // 暂时不验证页面
	rspBytes, err := mp.GetQRCode().GetWXACodeUnlimit(qrcode.QRCoder{
		Page:       page,
		Scene:      scene,
		EnvVersion: env,
		IsHyaline:  isHyaline,
		CheckPath:  &checkPath,
	})
	if err != nil {
		logx.Error(ctx, "mini check session err", logx.Any("err", err.Error()))
		err = errorx.New("小程序请求失败", -1)
	}
	var Rsp GetGetUnlimitedQRCodeRsp
	decodeErr := json.Unmarshal(rspBytes, &Rsp)
	if decodeErr != nil { // 成功返回图片buff,失败返回json
		// bbb := bytes.NewBuffer(bodyBytes) // buffer
		// m, _, _ := image.Decode(bbb)      // 图片文件解码
		// f, _ := os.Create("test.jpg")     // 创建文件
		// defer f.Close()                   // 关闭文件
		// jpeg.Encode(f, m, nil)            // 写入文件
		return base64.StdEncoding.EncodeToString(rspBytes), nil
	}

	if Rsp.ErrCode == 0 { // 成功
		return Rsp.Buffer, nil
	}
	return
}
